package cn.iocoder.yudao.module.infra.service.zip.impl;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.module.infra.api.zip.dto.FileDownloadItem;
import cn.iocoder.yudao.module.infra.api.zip.dto.ZipDownloadReqDTO;
import cn.iocoder.yudao.module.infra.api.zip.dto.ZipDownloadRespDTO;
import cn.iocoder.yudao.module.infra.service.zip.ZipDownloadService;
import lombok.extern.slf4j.Slf4j;
import net.lingala.zip4j.ZipFile;
import net.lingala.zip4j.model.ZipParameters;
import net.lingala.zip4j.model.enums.CompressionLevel;
import net.lingala.zip4j.model.enums.EncryptionMethod;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.FileInputStream;
import java.io.OutputStream;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * ZIP下载服务实现类
 *
 * @author 芋道源码
 */
@Service
@Slf4j
public class ZipDownloadServiceImpl implements ZipDownloadService {

    /**
     * 异步任务状态缓存
     */
    private final Map<String, ZipDownloadRespDTO> asyncTaskCache = new ConcurrentHashMap<>();

    @Override
    public ZipDownloadRespDTO downloadZip(ZipDownloadReqDTO reqDTO) {
        log.info("[downloadZip][开始下载ZIP] urls: {}, zipFileName: {}", reqDTO.getFiles().size(), reqDTO.getZipFileName());
        
        LocalDateTime startTime = LocalDateTime.now();
        List<ZipDownloadRespDTO.FileDownloadResult> successFiles = new ArrayList<>();
        List<ZipDownloadRespDTO.FileDownloadResult> failedFiles = new ArrayList<>();
        List<File> tempFiles = new ArrayList<>();
        
        try {
            // 参数验证
            validateRequest(reqDTO);
            
            // 创建临时目录
            File tempDir = createTempDirectory();
            File zipFile = new File(tempDir, reqDTO.getZipFileName());
            
            // 创建ZIP文件
            ZipFile zip = new ZipFile(zipFile);
            if (StrUtil.isNotBlank(reqDTO.getPassword())) {
                zip.setPassword(reqDTO.getPassword().toCharArray());
            }
            
            // 下载文件并添加到ZIP - 使用FileDownloadItem
            for (FileDownloadItem fileItem : reqDTO.getFiles()) {
                try {
                    ZipDownloadRespDTO.FileDownloadResult result = downloadAndAddToZip(fileItem, reqDTO, zip, tempFiles);
                    if ("SUCCESS".equals(result.getStatus())) {
                        successFiles.add(result);
                    } else {
                        failedFiles.add(result);

                        // 如果是必需文件且不跳过失败文件，则抛出异常
                        if (fileItem.isRequired() && !reqDTO.getSkipFailedFiles()) {
                            throw new ServiceException(500, "必需文件下载失败: " + result.getErrorMessage());
                        }
                    }
                } catch (Exception e) {
                    log.error("[downloadZip][下载文件失败] url: {}", fileItem.getUrl(), e);
                    ZipDownloadRespDTO.FileDownloadResult failedResult = ZipDownloadRespDTO.FileDownloadResult.builder()
                        .url(fileItem.getUrl())
                        .fileName(fileItem.getFinalFileName())
                        .status("FAILED")
                        .errorMessage(e.getMessage())
                        .build();
                    failedFiles.add(failedResult);

                    // 如果是必需文件且不跳过失败文件，则抛出异常
                    if (fileItem.isRequired() && !reqDTO.getSkipFailedFiles()) {
                        throw new ServiceException(500, "必需文件下载失败: " + e.getMessage());
                    }
                }
            }
            
            LocalDateTime endTime = LocalDateTime.now();
            long totalTimeMs = java.time.Duration.between(startTime, endTime).toMillis();
            
            // 构建响应
            return ZipDownloadRespDTO.builder()
                .zipFilePath(zipFile.getAbsolutePath())
                .zipFileSize(zipFile.length())
                .zipFileName(reqDTO.getZipFileName())
                .successCount(successFiles.size())
                .failedCount(failedFiles.size())
                .totalCount(reqDTO.getFiles().size())
                .startTime(startTime)
                .endTime(endTime)
                .totalTimeMs(totalTimeMs)
                .successFiles(successFiles)
                .failedFiles(failedFiles)
                .build();
                
        } catch (Exception e) {
            log.error("[downloadZip][ZIP下载异常]", e);
            throw new ServiceException(500, "ZIP下载失败: " + e.getMessage());
        } finally {
            // 清理临时文件
            cleanupTempFiles(null, tempFiles);
        }
    }

    @Override
    public void downloadZipStream(ZipDownloadReqDTO reqDTO, OutputStream os) {
        log.info("[downloadZipStream][开始流式下载ZIP] urls: {}, zipFileName: {}", reqDTO.getFiles().size(), reqDTO.getZipFileName());

        File zipFile = null;
        try {

            // 创建ZIP文件
            ZipDownloadRespDTO result = downloadZip(reqDTO);
            
            // 读取ZIP文件并写入响应流
            zipFile = new File(result.getZipFilePath());
            try (FileInputStream fis = new FileInputStream(zipFile)) {
                IoUtil.copy(fis, os);
                os.flush();
            }
            

            
        } catch (Exception e) {
            log.error("[downloadZipStream][流式下载ZIP异常]", e);
            throw new ServiceException(500, "流式下载ZIP失败: " + e.getMessage());
        } finally {
            IoUtil.close(os);
            // 清理临时文件
            FileUtil.del(zipFile.getParentFile());
        }
    }

    @Override
    public String downloadZipAsync(ZipDownloadReqDTO reqDTO) {
        String taskId = IdUtil.simpleUUID();
        log.info("[downloadZipAsync][开始异步下载ZIP] taskId: {}, urls: {}", taskId, reqDTO.getFiles().size());

        // 初始化任务状态
        ZipDownloadRespDTO initialResult = ZipDownloadRespDTO.builder()
            .zipFileName(reqDTO.getZipFileName())
            .totalCount(reqDTO.getFiles().size())
            .successCount(0)
            .failedCount(0)
            .startTime(LocalDateTime.now())
            .build();
        asyncTaskCache.put(taskId, initialResult);

        // 异步执行下载任务
        executeAsyncDownload(taskId, reqDTO);

        return taskId; // 立即返回taskId
    }

    /**
     * 异步执行下载任务
     */
    @Async
    private void executeAsyncDownload(String taskId, ZipDownloadReqDTO reqDTO) {
        try {
            log.info("[executeAsyncDownload][开始执行异步下载] taskId: {}", taskId);

            // 执行下载
            ZipDownloadRespDTO result = downloadZip(reqDTO);

            // 更新任务状态
            asyncTaskCache.put(taskId, result);

            log.info("[executeAsyncDownload][异步下载ZIP完成] taskId: {}, success: {}, failed: {}",
                taskId, result.getSuccessCount(), result.getFailedCount());

        } catch (Exception e) {
            log.error("[executeAsyncDownload][异步下载ZIP异常] taskId: {}", taskId, e);

            // 更新失败状态
            ZipDownloadRespDTO errorResult = ZipDownloadRespDTO.builder()
                .zipFileName(reqDTO.getZipFileName())
                .totalCount(reqDTO.getFiles().size())
                .successCount(0)
                .failedCount(reqDTO.getFiles().size())
                .startTime(LocalDateTime.now())
                .endTime(LocalDateTime.now())
                .build();
            asyncTaskCache.put(taskId, errorResult);
        }
    }

    @Override
    public ZipDownloadRespDTO getAsyncTaskStatus(String taskId) {
        return asyncTaskCache.get(taskId);
    }

    @Override
    public File createZipFile(ZipDownloadReqDTO reqDTO) {
        try {
            File tempDir = createTempDirectory();
            return new File(tempDir, reqDTO.getZipFileName());
        } catch (Exception e) {
            throw new ServiceException(500, "创建ZIP文件失败: " + e.getMessage());
        }
    }

    @Override
    public void cleanupTempFiles(File zipFile, List<File> tempFiles) {
        try {
            // 清理临时文件
            if (tempFiles != null) {
                for (File tempFile : tempFiles) {
                    FileUtil.del(tempFile);
                }
            }
            
            // 清理ZIP文件的父目录
            if (zipFile != null && zipFile.getParentFile() != null) {
                FileUtil.del(zipFile.getParentFile());
            }
        } catch (Exception e) {
            log.warn("[cleanupTempFiles][清理临时文件失败]", e);
        }
    }

    /**
     * 参数验证
     */
    private void validateRequest(ZipDownloadReqDTO reqDTO) {
        if (reqDTO.getFiles().size() > reqDTO.getMaxFileCount()) {
            throw new ServiceException(400, "文件数量超过限制: " + reqDTO.getMaxFileCount());
        }
        
        if (StrUtil.isBlank(reqDTO.getZipFileName())) {
            throw new ServiceException(400, "ZIP文件名不能为空");
        }
        
        if (!reqDTO.getZipFileName().toLowerCase().endsWith(".zip")) {
            reqDTO.setZipFileName(reqDTO.getZipFileName() + ".zip");
        }
    }

    /**
     * 创建临时目录
     */
    private File createTempDirectory() {
        String tempDir = System.getProperty("java.io.tmpdir");
        File dir = new File(tempDir, "zip_download_" + IdUtil.simpleUUID());
        if (!dir.mkdirs()) {
            throw new ServiceException(500, "创建临时目录失败");
        }
        return dir;
    }

    /**
     * 下载文件并添加到ZIP（充分利用FileDownloadItem的属性）
     */
    private ZipDownloadRespDTO.FileDownloadResult downloadAndAddToZip(FileDownloadItem fileItem, ZipDownloadReqDTO reqDTO,
                                                                     ZipFile zip, List<File> tempFiles) {
        long startTime = System.currentTimeMillis();

        try {
            // 下载文件
            File tempFile = downloadFile(fileItem, reqDTO);
            tempFiles.add(tempFile);

            // 检查文件大小
            long fileSizeMB = tempFile.length() / (1024 * 1024);
            int maxSize = fileItem.getEffectiveMaxSizeMB(reqDTO.getMaxFileSizeMB());
            if (fileSizeMB > maxSize) {
                throw new ServiceException(400, "文件大小超过限制: " + fileSizeMB + "MB > " + maxSize + "MB");
            }

            // 添加到ZIP
            ZipParameters zipParameters = new ZipParameters();
            zipParameters.setFileNameInZip(fileItem.getFinalFilePath());
            zipParameters.setCompressionLevel(getCompressionLevel(reqDTO.getCompressionLevel()));

            if (StrUtil.isNotBlank(reqDTO.getPassword())) {
                zipParameters.setEncryptFiles(true);
                zipParameters.setEncryptionMethod(EncryptionMethod.ZIP_STANDARD);
            }

            zip.addFile(tempFile, zipParameters);

            long endTime = System.currentTimeMillis();

            return ZipDownloadRespDTO.FileDownloadResult.builder()
                .url(fileItem.getUrl())
                .fileName(fileItem.getFinalFileName())
                .fileSize(tempFile.length())
                .status("SUCCESS")
                .downloadTimeMs(endTime - startTime)
                .build();

        } catch (Exception e) {
            long endTime = System.currentTimeMillis();

            return ZipDownloadRespDTO.FileDownloadResult.builder()
                .url(fileItem.getUrl())
                .fileName(fileItem.getFinalFileName())
                .status("FAILED")
                .errorMessage(e.getMessage())
                .downloadTimeMs(endTime - startTime)
                .build();
        }
    }

    /**
     * 下载文件（充分利用FileDownloadItem的属性）
     */
    private File downloadFile(FileDownloadItem fileItem, ZipDownloadReqDTO reqDTO) {
        String fileName = fileItem.getFinalFileName();
        File tempFile = new File(System.getProperty("java.io.tmpdir"), "download_" + IdUtil.simpleUUID() + "_" + fileName);

        int retryCount = fileItem.getEffectiveRetryCount();
        Exception lastException = null;

        // 重试机制
        for (int i = 0; i <= retryCount; i++) {
            try {
                // 设置下载参数
                int timeout = fileItem.getEffectiveTimeout(reqDTO.getDownloadTimeout()) * 1000;

                // 构建请求头
                Map<String, String> headers = new HashMap<>();
                headers.put("User-Agent", reqDTO.getUserAgent());

                // 设置全局请求头
                if (reqDTO.getHeaders() != null) {
                    headers.putAll(reqDTO.getHeaders());
                }

                // 设置文件特定的请求头
                if (fileItem.getHeaders() != null) {
                    headers.putAll(fileItem.getHeaders());
                }

                // 下载文件
                HttpUtil.downloadFile(fileItem.getUrl(), tempFile, timeout);

                return tempFile;

            } catch (Exception e) {
                lastException = e;
                if (i < retryCount) {
                    log.warn("[downloadFile][下载失败，准备重试] url: {}, retry: {}/{}", fileItem.getUrl(), i + 1, retryCount);
                    try {
                        Thread.sleep(1000 * (i + 1)); // 递增延迟
                    } catch (InterruptedException ie) {
                        Thread.currentThread().interrupt();
                        break;
                    }
                }
            }
        }

        throw new ServiceException(500, "下载文件失败（重试" + retryCount + "次后）: " + lastException.getMessage());
    }

    /**
     * 获取压缩级别
     */
    private CompressionLevel getCompressionLevel(Integer level) {
        if (level == null) {
            return CompressionLevel.NORMAL;
        }
        
        switch (level) {
            case 0: return CompressionLevel.NO_COMPRESSION;
            case 1: return CompressionLevel.FASTEST;
            case 2:
            case 3: return CompressionLevel.FAST;
            case 4:
            case 5:
            case 6: return CompressionLevel.NORMAL;
            case 7:
            case 8: return CompressionLevel.MAXIMUM;
            case 9: return CompressionLevel.ULTRA;
            default: return CompressionLevel.NORMAL;
        }
    }

}
